🎲 Bayesian Optimization

📅 3 января 2026

🔷 1. Суть

Макс: f(x), где оценка f(x) дорогая

🔷 2. Базовый код (scikit-optimize)

from skopt import gp_minimize
from skopt.space import Real, Integer

# Определить пространство поиска
space = [
    Real(1e-6, 1e-1, prior='log-uniform', name='learning_rate'),
    Integer(10, 100, name='n_estimators'),
    Integer(3, 10, name='max_depth')
]

# Целевая функция (минимизируем, например, -accuracy)
def objective(params):
    lr, n_est, depth = params
    
    model = RandomForestClassifier(
        n_estimators=n_est,
        max_depth=depth,
        random_state=42
    )
    model.fit(X_train, y_train)
    score = model.score(X_val, y_val)
    
    return -score  # минимизируем, поэтому отрицательный

# Bayesian Optimization
result = gp_minimize(
    objective,
    space,
    n_calls=50,       # число итераций
    random_state=42,
    verbose=True
)

print(f"Best params: {result.x}")
print(f"Best score: {-result.fun}")

🔷 3. Базовый код (Optuna)

import optuna

def objective(trial):
    # Определяем гиперпараметры
    lr = trial.suggest_loguniform('learning_rate', 1e-6, 1e-1)
    n_est = trial.suggest_int('n_estimators', 10, 100)
    depth = trial.suggest_int('max_depth', 3, 10)
    
    # Обучаем модель
    model = RandomForestClassifier(
        n_estimators=n_est,
        max_depth=depth,
        random_state=42
    )
    model.fit(X_train, y_train)
    
    # Возвращаем метрику (максимизируем)
    return model.score(X_val, y_val)

# Создать study
study = optuna.create_study(
    direction='maximize',
    sampler=optuna.samplers.TPESampler()
)

# Оптимизация
study.optimize(objective, n_trials=50)

print(f"Best params: {study.best_params}")
print(f"Best score: {study.best_value}")

🔷 4. Как работает

  1. Инициализация: несколько случайных точек
  2. Surrogate Model: обучить GP на текущих точках
  3. Acquisition Function: найти следующую точку
  4. Оценка: вычислить f(x) в новой точке
  5. Повторить: обновить GP и повторить

GP (Gaussian Process): дает предсказание + uncertainty

Acquisition Function: использует оба для выбора

Процесс Bayesian Optimization
Процесс Bayesian Optimization
Процесс Bayesian Optimization
Процесс Bayesian Optimization

🔷 5. Acquisition Functions

ФункцияОписаниеКогда использовать
EI (Expected Improvement)Ожидаемое улучшениеПо умолчанию, баланс
PI (Probability of Improvement)Вероятность улучшенияКонсервативный
UCB (Upper Confidence Bound)Верхняя граница доверияБольше exploration
LCB (Lower Confidence Bound)Нижняя границаМинимизация
Сравнение Acquisition Functions

🔷 6. Параметры оптимизации

ПараметрОписаниеСовет
n_callsЧисло итераций50-200, зависит от бюджета
n_initial_pointsСлучайных точек вначале10-20
acq_funcAcquisition function'EI', 'PI', 'gp_hedge'
kappaExploration для UCB1.96 по умолчанию
xiExploration для EI/PI0.01 по умолчанию

🔷 7. Типы пространств поиска

from skopt.space import Real, Integer, Categorical

space = [
    # Непрерывные параметры
    Real(0.001, 0.1, prior='log-uniform', name='lr'),
    Real(0.1, 0.9, name='dropout'),
    
    # Целочисленные параметры
    Integer(10, 200, name='n_estimators'),
    Integer(2, 10, name='max_depth'),
    
    # Категориальные параметры
    Categorical(['adam', 'sgd', 'rmsprop'], name='optimizer'),
    Categorical([32, 64, 128, 256], name='batch_size')
]

🔷 8. Продвинутый пример с CV

from skopt import gp_minimize
from sklearn.model_selection import cross_val_score

def objective(params):
    lr, n_est, depth = params
    
    model = RandomForestClassifier(
        n_estimators=n_est,
        max_depth=depth,
        random_state=42
    )
    
    # Кросс-валидация
    scores = cross_val_score(
        model, X_train, y_train,
        cv=5, scoring='accuracy'
    )
    
    # Минимизируем отрицательный score
    return -scores.mean()

result = gp_minimize(
    objective,
    space,
    n_calls=50,
    n_initial_points=10,
    acq_func='EI',
    random_state=42
)

print(f"Best: {result.x}, Score: {-result.fun:.4f}")

🔷 9. Визуализация результатов

from skopt.plots import plot_convergence, plot_objective
import matplotlib.pyplot as plt

# Convergence plot
plot_convergence(result)
plt.title('Convergence Plot')
plt.show()

# Objective function plot
plot_objective(result)
plt.tight_layout()
plt.show()

# История поиска
import pandas as pd
history = pd.DataFrame({
    'iteration': range(len(result.func_vals)),
    'score': -result.func_vals
})
history['best_so_far'] = history['score'].cummax()

plt.figure(figsize=(10, 6))
plt.plot(history['iteration'], history['score'], 'o', label='Score')
plt.plot(history['iteration'], history['best_so_far'], '-', label='Best so far')
plt.xlabel('Iteration')
plt.ylabel('Score')
plt.legend()
plt.title('Optimization Progress')
plt.show()
Итерации Bayesian Optimization
Итерации Bayesian Optimization
Итерации Bayesian Optimization
Итерации Bayesian Optimization

🔷 10. Преимущества и недостатки

✅ Преимущества

  • Эффективнее Grid/Random Search
  • Учитывает прошлые оценки
  • Хорошо для дорогих функций
  • Баланс exploration/exploitation
  • Меньше итераций для хорошего результата

❌ Недостатки

  • Медленнее на одну итерацию
  • Плохо масштабируется (>20 параметров)
  • Требует больше памяти
  • GP плохо работает с категориями
  • Не параллелизуется легко

🔷 11. Когда использовать

✅ Хорошо подходит

  • Дорогие вычисления (обучение модели)
  • Малый бюджет итераций (<200)
  • Непрерывное пространство
  • 2-20 параметров
  • Нужен оптимум, не просто "хорошо"

❌ Плохо подходит

  • Дешевые вычисления (используйте Grid Search)
  • Много параметров (>20)
  • Нужна параллелизация
  • Дискретное пространство с большим числом значений

🔷 12. BO vs Grid vs Random Search

МетодИтерацийЭффективностьКогда использовать
Grid SearchМногоНизкаяМалое пространство, дешево
Random SearchСреднеСредняяBaseline, высокие размерности
Bayesian OptМалоВысокаяДорого, малое число параметров

🔷 13. Использование с XGBoost

import xgboost as xgb
from skopt import gp_minimize
from skopt.space import Real, Integer

def objective(params):
    max_depth, learning_rate, n_estimators, subsample = params
    
    model = xgb.XGBClassifier(
        max_depth=max_depth,
        learning_rate=learning_rate,
        n_estimators=n_estimators,
        subsample=subsample,
        random_state=42,
        use_label_encoder=False,
        eval_metric='logloss'
    )
    
    model.fit(X_train, y_train)
    score = model.score(X_val, y_val)
    
    return -score

space = [
    Integer(3, 10, name='max_depth'),
    Real(0.01, 0.3, prior='log-uniform', name='learning_rate'),
    Integer(50, 300, name='n_estimators'),
    Real(0.5, 1.0, name='subsample')
]

result = gp_minimize(objective, space, n_calls=50)
print(f"Best params: {result.x}")

🔷 14. Параллелизация

# Optuna поддерживает параллелизацию
import optuna
from joblib import Parallel, delayed

def objective(trial):
    lr = trial.suggest_loguniform('lr', 1e-6, 1e-1)
    # ... обучение модели
    return score

# Параллельная оптимизация
study = optuna.create_study(direction='maximize')

# n_jobs=-1 использует все ядра
study.optimize(objective, n_trials=100, n_jobs=-1)

# Или ручная параллелизация (для skopt)
from skopt import Optimizer

opt = Optimizer(space, base_estimator='GP', acq_func='EI')

# Запросить несколько точек параллельно
x = opt.ask(n_points=4)

# Оценить параллельно
y = Parallel(n_jobs=4)(delayed(objective)(xi) for xi in x)

# Обновить optimizer
opt.tell(x, y)

🔷 15. Практические советы

🔷 16. Чек-лист

«Bayesian Optimization — золотой стандарт для подбора гиперпараметров когда вычисления дороги. Находит оптимум в 5-10 раз быстрее чем Random Search».

🔗 Полезные ссылки